﻿using System;
using System.Threading;
using System.Diagnostics;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Runtime.Serialization;
using System.Threading.Tasks;

namespace Nature.Text
{
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
    public class TextSourceAttribute : System.Attribute
    {
        public TextSourceAttribute(Type textSourceType)
        {
            this.TextSourceType = textSourceType;
        }

        public Type TextSourceType { get; private set; }
    }

    /// <summary>
    /// Generic text container
    /// </summary>
    [DebuggerDisplay("Length: {Length}")]
    [Serializable]
    public class TextSource :
        IDeserializationCallback,
        IEnumerable<TextSource.PositionView>
    {
        #region Private Fields

        private Task<Position[]> m_calcPositionsTask;
        private string m_adaptedText;

        #endregion

        /// <summary>
        /// Create in instance of the TextSource class
        /// </summary>
        /// <param name="text">original text</param>
        public TextSource(string text)
        {
            PreInitialize(text);
            if (this.GetType() == typeof(TextSource))
            {
                this.Initialize();
            }
        }

        protected void Initialize()
        {
            m_adaptedText = CreateAdaptedVersion();
            if (m_adaptedText.Length != OriginalText.Length)
            {
                throw new System.InvalidOperationException();
            }
        }


        /// <summary>
        /// Converts the given text to a simplified for parsing analog
        /// </summary>
        /// <param name="text">original text</param>
        /// <returns>adapted text</returns>
        /// <remarks>
        /// The conversion must preserve the text length
        /// </remarks>
        protected virtual string CreateAdaptedVersion() { return OriginalText; }

        private bool Applicable(Capture capture)
        {
            return (capture.Index < this.Length && capture.Index + capture.Length <= this.Length);
        }

        protected virtual string ToAdopted(string dataField) { return dataField; }

        #region Static Members
        public static readonly Regex RegexPrintableFieldsOnly = new Regex(@"^\S+$");
        public static readonly Regex RegexSinglePrintableField = new Regex(@"^\s*(\S+)\s*$");
        static readonly Regex RegexWhiteSpace = new Regex(@"\s+|(?=\#)(?<!\\)\#[^\r\n]*\s*");

        public static string NormalizePattern(string pattern)
        {
            return RegexWhiteSpace.Replace(pattern, string.Empty);
        }
        #endregion

        #region Nested Types
        /// <summary>
        /// Line - Column text coordinates pair
        /// </summary>
        [DebuggerDisplay("line: {line}, column {column}")]
        struct Position
        {
            public int line;
            public int column;
        }

        [DebuggerDisplay("Line: {Line}, Column {Column}")]
        public struct PositionView
        {
            private TextSource m_source;

            public int Index { get; set; }

            public int Line { get; set; }

            public int Column { get; set; }

            internal PositionView(TextSource source, int line, int column)
                : this()
            {
                m_source = source;
                Line = line;
                Column = column;
            }

            public override string ToString()
            {
                return this.ToString('.');
            }

            public string ToString(char filler)
            {
                if (Column < 1)
                {
                    return string.Empty;
                }
                string line = m_source.GetLine(this.Line);
                string pattern = string.Format("^.{0}{1}{2}", "{", Column, "}");
                line = Regex.Replace(line, pattern, new string(filler, Column));
                return line;
            }

            public string ToString(char filler, char pointer)
            {
                if (Column < 1)
                {
                    return string.Empty;
                }
                string line = m_source.GetLine(this.Line);
                return string.Format("{0}\r\n{1}{2}", line, new string(filler, Column - 1), pointer);
            }

        }
        #endregion

        #region Private Methods

        private void PreInitialize(string text)
        {
            text = Normalize(text);
            OriginalText = text;
            m_calcPositionsTask = Task.Factory.StartNew<Position[]>(() =>
            {
                Position[] positions = new Position[OriginalText.Length];
                for (int index = 0; index < positions.Length; ++index)
                {
                    Position previous = index == 0
                    ? new Position() { line = 1, column = 0 }
                    : positions[index - 1];
                    char character = OriginalText[index];
                    bool isBreak = (character == '\r' || character == '\n');
                    positions[index] = new Position()
                    {
                        line = previous.line + (isBreak && character == '\r' ? 1 : 0),
                        column = (isBreak
                                    ? (character == '\n' ? 0 : -1)
                                    : previous.column + 1)
                    };

                }
                return positions;
            });
        }




        private static string Normalize(string text)
        {
            Regex rgx = new Regex(@"(?=\n)(?<!\r)\n|\r(?!\n)",
                RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);
            text = rgx.Replace(text, m => "\r\n");
            return text;
        }

        #endregion



        /// <summary>
        /// Original text 
        /// </summary>
        public string OriginalText { get; private set; }

        /// <summary>
        /// Adapted text
        /// </summary>
        public string AdaptedText
        {
            get
            {
                if (m_adaptedText == null)
                {
                    throw new InvalidOperationException(
                        string.Format("TextSource of the {0} type has not been initialized", GetType().FullName));
                }
                return m_adaptedText;
            }
        }

        /// <summary>
        /// Text length
        /// </summary>
        public int Length
        {
            get
            {
                return OriginalText.Length;
            }
        }

        /// <summary>
        /// Gets the text position properties by the position index
        /// </summary>
        public PositionView this[int index]
        {
            get
            {
                if (index < 0 || index > Length)
                {
                    throw new System.ArgumentOutOfRangeException();
                }
                Position pos = m_calcPositionsTask.Result[index];
                return new PositionView(this, pos.line, pos.column) { Index = index };
            }
        }

        /// <summary>
        /// Gets the original text line by the given line number
        /// </summary>        
        public string GetLine(int lineNumber)
        {
            Regex rgx = new Regex(@"^.*$", RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline);
            int counter = 0;
            for (Match mch = rgx.Match(OriginalText); mch.Success; mch = mch.NextMatch())
            {
                if (++counter == lineNumber)
                {
                    return mch.Value;
                }
            }
            return null;
        }



        public override string ToString()
        {
            return OriginalText;
        }

        #region IDeserializationCallback Members

        void IDeserializationCallback.OnDeserialization(object sender)
        {

        }

        #endregion

        #region IEnumerable<PositionView> Members

        IEnumerator<TextSource.PositionView> IEnumerable<TextSource.PositionView>.GetEnumerator()
        {
            for (int index = 0; index < Length; ++index)
            {
                yield return this[index];
            }
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            for (int index = 0; index < Length; ++index)
            {
                yield return this[index];
            }
        }

        #endregion

    }
}
